/****************************************************************************
 * Copyright (c) 2006 Ecliptical Software Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     Ecliptical Software Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.pubsub.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.sharedobject.ISharedObject;
import org.eclipse.ecf.core.sharedobject.ISharedObjectConfig;
import org.eclipse.ecf.core.sharedobject.ISharedObjectContext;
import org.eclipse.ecf.core.sharedobject.ISharedObjectManager;
import org.eclipse.ecf.core.sharedobject.ReplicaSharedObjectDescription;
import org.eclipse.ecf.core.sharedobject.SharedObjectInitException;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectActivatedEvent;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectCreateResponseEvent;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectDeactivatedEvent;
import org.eclipse.ecf.core.util.Event;
import org.eclipse.ecf.pubsub.IPublishedService;
import org.eclipse.ecf.pubsub.ISubscribedService;
import org.eclipse.ecf.pubsub.ISubscriber;
import org.eclipse.ecf.pubsub.ISubscription;
import org.eclipse.ecf.pubsub.ISubscriptionCallback;

public class SubscriptionAgent extends PlatformObject implements ISharedObject, ISubscriber {
	
	protected static final Object CONTAINER_ID_KEY = Integer.valueOf(0);
	
	protected static final Object SHARED_OBJECT_ID_KEY = Integer.valueOf(1);
	
	protected static final Object CALLBACK_KEY = Integer.valueOf(2);

	protected ISharedObjectConfig config;
	
	protected ID containerID;
	
	protected ID sharedObjectID;
	
	protected ISubscriptionCallback callback;
	
	protected ISubscribedService svc;
	
	protected boolean disposed;
	
	public synchronized void subscribed(ISubscribedService svc) {
		if (disposed)
			throw new IllegalStateException("Already disposed.");

		this.svc = svc;
		callback.subscribed(new Subscription());
	}
	
	public void init(ISharedObjectConfig config) throws SharedObjectInitException {
		Map props = config.getProperties();
		
		if (config.getContext().getLocalContainerID().equals(config.getHomeContainerID())) { 
			containerID = (ID) props.get(CONTAINER_ID_KEY);
			if (containerID == null)
				throw new SharedObjectInitException("containerID is required");

			callback = (ISubscriptionCallback) props.get(CALLBACK_KEY);
			if (callback == null)
				throw new SharedObjectInitException("callback is required");
		}
		
		sharedObjectID = (ID) props.get(SHARED_OBJECT_ID_KEY);
		if (sharedObjectID == null)
			throw new SharedObjectInitException("sharedObjectID is required");

		this.config = config;
	}

	public void handleEvent(Event event) {
		if (event instanceof ISharedObjectActivatedEvent) {
			ISharedObjectActivatedEvent e = (ISharedObjectActivatedEvent) event;
			if (e.getActivatedID().equals(config.getSharedObjectID()))
				activated();
		} else if (event instanceof ISharedObjectDeactivatedEvent) {
			ISharedObjectDeactivatedEvent e = (ISharedObjectDeactivatedEvent) event;
			if (e.getDeactivatedID().equals(config.getSharedObjectID()))
				deactivated();
		} else if (event instanceof ISharedObjectCreateResponseEvent)
			received((ISharedObjectCreateResponseEvent) event);
	}
	
	protected boolean isPrimary() {
		return config.getContext().getLocalContainerID().equals(config.getHomeContainerID());
	}
	
	protected void activated() {
		ISharedObjectContext ctx = config.getContext();
		if (isPrimary()) {
			try {
				ctx.sendCreate(containerID, createReplicaDescription());
			} catch (IOException e) {
				callback.requestFailed(e);
				ctx.getSharedObjectManager().removeSharedObject(config.getSharedObjectID());
			}

			return;
		}
		
		ISharedObjectManager mgr = ctx.getSharedObjectManager();
		ISharedObject so = mgr.getSharedObject(sharedObjectID);
		try {
			ID homeContainerID = config.getHomeContainerID();
			if (so instanceof IPublishedService) {
				IPublishedService svc = (IPublishedService) so;
				svc.subscribe(homeContainerID, config.getSharedObjectID());
			} else {
				ctx.sendCreateResponse(homeContainerID, new IllegalArgumentException("Not an IPublishedService."), -1);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			ctx.getSharedObjectManager().removeSharedObject(config.getSharedObjectID());
		}
	}
	
	protected void deactivated() {
		if (isPrimary()) {
			synchronized (this) {
				disposed = true;
				if (svc != null)
					svc.unsubscribe(config.getSharedObjectID());
			}
		}
	}
	
	protected void received(ISharedObjectCreateResponseEvent e) {
		if (e.getRemoteContainerID().equals(containerID) && e.getSenderSharedObjectID().equals(config.getSharedObjectID())) {
			callback.requestFailed(e.getException());
			config.getContext().getSharedObjectManager().removeSharedObject(config.getSharedObjectID());
		}
	}
	
	protected ReplicaSharedObjectDescription createReplicaDescription() {
		Map props = new HashMap(1);
		props.put(SHARED_OBJECT_ID_KEY, sharedObjectID);
		return new ReplicaSharedObjectDescription(getClass(), config.getSharedObjectID(), config.getHomeContainerID(), props);
	}

	public void handleEvents(Event[] events) {
		for (int i = 0; i < events.length; ++i)
			handleEvent(events[i]);
	}
	
	public void dispose(ID containerID) {
		config = null;
		callback = null;
		svc = null;
	}
	
	protected class Subscription implements ISubscription {

		public ID getID() {
			return sharedObjectID;
		}

		public ID getHomeContainerID() {
			return containerID;
		}
		
		public ISubscribedService getSubscribedService() {
			return svc;
		}

		public void dispose() {
			synchronized (SubscriptionAgent.this) {
				if (!disposed)
					config.getContext().getSharedObjectManager().removeSharedObject(config.getSharedObjectID());
			}
		}
	}
}
